1use super::packed::Packed;
10use crate::nn;
11use crate::quant::QLinear;
12use anyhow::{Context, Result};
13use half::bf16;
14use rayon::prelude::*;
15
16pub struct HcWeights<'a> {
17 pub norm: &'a [bf16],
18 pub down: QLinear<'a>,
19 pub up: QLinear<'a>,
20 pub inject: Option<QLinear<'a>>,
21}
22
23pub struct AttnWeights<'a> {
24 pub q_proj: QLinear<'a>,
25 pub k_proj: QLinear<'a>,
26 pub v_proj: QLinear<'a>,
27 pub o_proj: QLinear<'a>,
28 pub q_norm: &'a [bf16],
29 pub k_norm: &'a [bf16],
30 pub index_qk: QLinear<'a>,
31 pub index_q_norm: &'a [bf16],
32 pub index_k_norm: &'a [bf16],
33}
34
35pub struct DeltaWeights<'a> {
36 pub in_proj_qkv: QLinear<'a>,
37 pub in_proj_z: QLinear<'a>,
38 pub in_proj_a: QLinear<'a>,
39 pub in_proj_b: QLinear<'a>,
40 pub conv1d: &'a [bf16],
41 pub a_log: &'a [bf16],
42 pub dt_bias: &'a [bf16],
43 pub norm: &'a [bf16],
44 pub out_proj: QLinear<'a>,
45}
46
47pub enum Mixer<'a> {
48 Attn(AttnWeights<'a>),
49 Delta(DeltaWeights<'a>),
50}
51
52pub struct MoeWeights<'a> {
53 pub router: &'a [bf16],
55 pub shared_gate: &'a [bf16],
56 pub shared_up: QLinear<'a>,
57 pub shared_gate_proj: QLinear<'a>,
58 pub shared_down: QLinear<'a>,
59 pub record_layer: usize,
60}
61
62pub struct PleWeights<'a> {
63 pub key_proj: QLinear<'a>,
64 pub value_proj: QLinear<'a>,
65 pub norm_key: &'a [bf16],
66 pub norm_query: &'a [bf16],
67 pub norm_conv: &'a [bf16],
68 pub conv1d: &'a [bf16],
70 pub kernel: usize,
71 pub dilation: usize,
72 pub multipliers: Vec<i64>,
73 pub head_offsets: Vec<u64>,
74 pub head_sizes: Vec<u64>,
75}
76
77pub struct Layer<'a> {
78 pub attn_hc: HcWeights<'a>,
79 pub mlp_hc: HcWeights<'a>,
80 pub mixer: Mixer<'a>,
81 pub moe: MoeWeights<'a>,
82 pub ple: Option<PleWeights<'a>>,
83}
84
85pub struct MtpWeights<'a> {
89 pub enorm: &'a [bf16],
92 pub hnorm: &'a [bf16],
93 pub fc_e: QLinear<'a>,
94 pub fc_h: QLinear<'a>,
95 pub layer: Layer<'a>,
96 pub mixer: HcWeights<'a>,
97}
98
99pub struct CpuModel<'a> {
100 pub p: &'a Packed,
101 pub embed: QLinear<'a>,
102 pub layers: Vec<Layer<'a>>,
103 pub final_mixer: HcWeights<'a>,
104 pub lm_head: QLinear<'a>,
105 pub mtp: Option<MtpWeights<'a>>,
106}
107
108#[derive(Default)]
109pub struct KvCache {
110 pub k: Vec<f32>,
112 pub v: Vec<f32>,
113 pub index_k: Vec<f32>,
115 pub len: usize,
116}
117
118pub struct DeltaState {
119 pub s: Vec<f32>,
120 pub conv: Vec<f32>,
121}
122
123pub struct PleState {
124 pub hist: Vec<f32>,
126 pub span: usize,
127 pub filled: usize,
128}
129
130pub struct State {
131 pub pos: usize,
132 pub tokens: Vec<u32>,
133 pub kv: Vec<KvCache>,
134 pub delta: Vec<DeltaState>,
135 pub ple: Option<PleState>,
136 pub last_hyper: Vec<f32>,
139 pub mtp_kv: KvCache,
141}
142
143fn hc<'a>(p: &'a Packed, prefix: &str, inject: bool) -> Result<HcWeights<'a>> {
144 Ok(HcWeights {
145 norm: p.bf16(&format!("{prefix}.hc_norm.weight"))?,
146 down: p.qlinear(&format!("{prefix}.input_mix_weight_down"))?,
147 up: p.qlinear(&format!("{prefix}.input_mix_weight_up"))?,
148 inject: if inject {
149 Some(p.qlinear(&format!("{prefix}.block_inject_weight"))?)
150 } else {
151 None
152 },
153 })
154}
155
156impl<'a> CpuModel<'a> {
157 pub fn load(p: &'a Packed) -> Result<Self> {
158 let c = &p.cfg;
159 let m = "language_model.model";
160 let mut layers = Vec::with_capacity(c.num_hidden_layers);
161
162 for l in 0..c.num_hidden_layers {
163 let lp = format!("{m}.layers.{l}");
164
165 layers.push(load_layer(
166 p,
167 &lp,
168 c.is_linear(l),
169 p.expert_layer(l),
170 c.ple_layer() == Some(l),
171 )?);
172 }
173
174 let mtp = if c.mtp_num_hidden_layers > 0 {
175 Some(MtpWeights {
176 enorm: p.bf16("mtp.pre_fc_norm_embedding.weight")?,
177 hnorm: p.bf16("mtp.pre_fc_norm_hidden.weight")?,
178 fc_e: p.qlinear("mtp.fc_embedding")?,
179 fc_h: p.qlinear("mtp.fc_hidden")?,
180 layer: load_layer(p, "mtp.layers.0", false, p.mtp_expert_layer(0)?, false)?,
181 mixer: hc(p, "mtp.hyper_connection_mixer", false)?,
182 })
183 } else {
184 None
185 };
186
187 Ok(CpuModel {
188 p,
189 embed: p.qlinear(&format!("{m}.embed_tokens"))?,
190 layers,
191 final_mixer: hc(p, &format!("{m}.hyper_connection_mixer"), false)?,
192 lm_head: p.qlinear("language_model.lm_head")?,
193 mtp,
194 })
195 }
196}
197
198fn load_layer<'a>(
199 p: &'a Packed,
200 lp: &str,
201 linear: bool,
202 record_layer: usize,
203 with_ple: bool,
204) -> Result<Layer<'a>> {
205 let c = &p.cfg;
206 let mixer = if linear {
207 let d = format!("{lp}.linear_attn");
208
209 Mixer::Delta(DeltaWeights {
210 in_proj_qkv: p.qlinear(&format!("{d}.in_proj_qkv"))?,
211 in_proj_z: p.qlinear(&format!("{d}.in_proj_z"))?,
212 in_proj_a: p.qlinear(&format!("{d}.in_proj_a"))?,
213 in_proj_b: p.qlinear(&format!("{d}.in_proj_b"))?,
214 conv1d: p.bf16(&format!("{d}.conv1d.weight"))?,
215 a_log: p.bf16(&format!("{d}.A_log"))?,
216 dt_bias: p.bf16(&format!("{d}.dt_bias"))?,
217 norm: p.bf16(&format!("{d}.norm.weight"))?,
218 out_proj: p.qlinear(&format!("{d}.out_proj"))?,
219 })
220 } else {
221 let a = format!("{lp}.self_attn");
222
223 Mixer::Attn(AttnWeights {
224 q_proj: p.qlinear(&format!("{a}.q_proj"))?,
225 k_proj: p.qlinear(&format!("{a}.k_proj"))?,
226 v_proj: p.qlinear(&format!("{a}.v_proj"))?,
227 o_proj: p.qlinear(&format!("{a}.o_proj"))?,
228 q_norm: p.bf16(&format!("{a}.q_norm.weight"))?,
229 k_norm: p.bf16(&format!("{a}.k_norm.weight"))?,
230 index_qk: p.qlinear(&format!("{a}.indexer.index_qk_proj"))?,
231 index_q_norm: p.bf16(&format!("{a}.indexer.q_layernorm.weight"))?,
232 index_k_norm: p.bf16(&format!("{a}.indexer.k_layernorm.weight"))?,
233 })
234 };
235 let moe = MoeWeights {
236 router: p.bf16(&format!("{lp}.mlp.gate.weight"))?,
237 shared_gate: p.bf16(&format!("{lp}.mlp.shared_expert_gate.weight"))?,
238 shared_gate_proj: p.qlinear(&format!("{lp}.mlp.shared_expert.gate_proj"))?,
239 shared_up: p.qlinear(&format!("{lp}.mlp.shared_expert.up_proj"))?,
240 shared_down: p.qlinear(&format!("{lp}.mlp.shared_expert.down_proj"))?,
241 record_layer,
242 };
243 let ple = if with_ple {
244 let pp = format!("{lp}.ple");
245 let conv = p.bf16(&format!("{pp}.conv1d.weight"))?;
246 let kernel = p.shape(&format!("{pp}.conv1d.weight"))?[2];
247 let ngram = p.ngram_metadata(&format!("{pp}.ple_embedding"))?;
248
249 Some(PleWeights {
250 key_proj: p.qlinear(&format!("{pp}.key_proj"))?,
251 value_proj: p.qlinear(&format!("{pp}.value_proj"))?,
252 norm_key: p.bf16(&format!("{pp}.norm_key.weight"))?,
253 norm_query: p.bf16(&format!("{pp}.norm_query.weight"))?,
254 norm_conv: p.bf16(&format!("{pp}.norm_conv.weight"))?,
255 conv1d: conv,
256 kernel,
257 dilation: c.ngram_size,
258 multipliers: ngram.multipliers,
259 head_offsets: ngram.head_offsets,
260 head_sizes: ngram.head_sizes,
261 })
262 } else {
263 None
264 };
265
266 Ok(Layer {
267 attn_hc: hc(p, &format!("{lp}.attn_hyper_connection"), true)?,
268 mlp_hc: hc(p, &format!("{lp}.mlp_hyper_connection"), true)?,
269 mixer,
270 moe,
271 ple,
272 })
273}
274
275impl<'a> CpuModel<'a> {
276 pub fn new_state(&self) -> State {
277 let c = &self.p.cfg;
278 let mut kv = Vec::new();
279 let mut delta = Vec::new();
280
281 for l in 0..c.num_hidden_layers {
282 if c.is_linear(l) {
283 delta.push(DeltaState {
284 s: vec![
285 0.0;
286 c.linear_num_value_heads
287 * c.linear_key_head_dim
288 * c.linear_value_head_dim
289 ],
290 conv: vec![
291 0.0;
292 (2 * c.linear_num_key_heads * c.linear_key_head_dim
293 + c.linear_num_value_heads * c.linear_value_head_dim)
294 * (c.linear_conv_kernel_dim - 1)
295 ],
296 });
297 } else {
298 kv.push(KvCache::default());
299 }
300 }
301
302 let ple = c.ple_layer().map(|_| {
303 let span = (c.ple_conv_kernel_size - 1) * c.ngram_size;
304
305 PleState {
306 hist: vec![0.0; span * c.hc_hidden()],
307 span,
308 filled: 0,
309 }
310 });
311
312 State {
313 pos: 0,
314 tokens: Vec::new(),
315 kv,
316 delta,
317 ple,
318 last_hyper: Vec::new(),
319 mtp_kv: KvCache::default(),
320 }
321 }
322
323 pub fn mtp_forward(
329 &self,
330 token: u32,
331 hyper_in: &[f32],
332 pos: usize,
333 state: &mut State,
334 ) -> Result<(Vec<f32>, Vec<f32>)> {
335 let w = self.mtp.as_ref().context("no MTP head")?;
336 let c = &self.p.cfg;
337 let h = c.hidden_size;
338 let hc = c.hc_count;
339 let eps = c.rms_norm_eps as f32;
340
341 anyhow::ensure!(hyper_in.len() == h * hc, "hyper input width");
342
343 let mut e = vec![0.0f32; h];
344
345 self.embed.dequant_row(token as usize, &mut e);
346
347 let en = rms_norm_shift(&e, w.enorm, eps);
348 let mut fe = vec![0.0f32; h];
349
350 w.fc_e.matvec(&en, &mut fe);
351
352 let mut hyper = vec![0.0f32; h * hc];
353 let mut fh = vec![0.0f32; h];
354
355 for g in 0..hc {
356 let hn = rms_norm_shift(
357 &hyper_in[g * h..(g + 1) * h],
358 &w.hnorm[g * h..(g + 1) * h],
359 eps,
360 );
361
362 w.fc_h.matvec(&hn, &mut fh);
363
364 for i in 0..h {
365 hyper[g * h + i] = fe[i] + fh[i];
366 }
367 }
368
369 let layer = &w.layer;
370 let (mixed, inj) = self.gated_residual(&layer.attn_hc, &hyper, eps);
371 let out = match &layer.mixer {
372 Mixer::Attn(a) => self.attention(a, &mixed, &mut state.mtp_kv, pos)?,
373 Mixer::Delta(_) => anyhow::bail!("MTP layer is expected to be full attention"),
374 };
375
376 inject(&mut hyper, &out, &inj);
377
378 let (mixed, inj) = self.gated_residual(&layer.mlp_hc, &hyper, eps);
379 let out = self.moe(&layer.moe, &mixed);
380
381 inject(&mut hyper, &out, &inj);
382
383 let (mixed, _) = self.gated_residual(&w.mixer, &hyper, eps);
384 let mut logits = vec![0.0f32; self.lm_head.out_dim];
385
386 self.lm_head.matvec(&mixed, &mut logits);
387
388 Ok((logits, hyper))
389 }
390
391 pub fn forward_token(&self, token: u32, state: &mut State) -> Result<Vec<f32>> {
393 let c = &self.p.cfg;
394 let h = c.hidden_size;
395 let hc = c.hc_count;
396 let hh = h * hc;
397 let eps = c.rms_norm_eps as f32;
398
399 let mut e = vec![0.0f32; h];
400
401 self.embed.dequant_row(token as usize, &mut e);
402
403 let mut hyper = vec![0.0f32; hh];
404
405 for g in 0..hc {
406 hyper[g * h..(g + 1) * h].copy_from_slice(&e);
407 }
408
409 state.tokens.push(token);
410
411 let mut kv_idx = 0;
412 let mut delta_idx = 0;
413
414 for layer in &self.layers {
415 if let Some(ple) = &layer.ple {
416 let add = self.ple(ple, &hyper, state)?;
417
418 for (x, a) in hyper.iter_mut().zip(&add) {
419 *x += a;
420 }
421 }
422
423 let (mixed, inj) = self.gated_residual(&layer.attn_hc, &hyper, eps);
424 let out = match &layer.mixer {
425 Mixer::Attn(a) => {
426 let o = self.attention(a, &mixed, &mut state.kv[kv_idx], state.pos)?;
427 kv_idx += 1;
428
429 o
430 }
431 Mixer::Delta(d) => {
432 let o = self.deltanet(d, &mixed, &mut state.delta[delta_idx]);
433 delta_idx += 1;
434
435 o
436 }
437 };
438
439 inject(&mut hyper, &out, &inj);
440
441 let (mixed, inj) = self.gated_residual(&layer.mlp_hc, &hyper, eps);
442 let out = self.moe(&layer.moe, &mixed);
443
444 inject(&mut hyper, &out, &inj);
445 }
446
447 state.pos += 1;
448
449 state.last_hyper.clone_from(&hyper);
450
451 let (mixed, _) = self.gated_residual(&self.final_mixer, &hyper, eps);
452 let mut logits = vec![0.0f32; self.lm_head.out_dim];
453
454 self.lm_head.matvec(&mixed, &mut logits);
455
456 Ok(logits)
457 }
458
459 fn gated_residual(&self, w: &HcWeights, hyper: &[f32], eps: f32) -> (Vec<f32>, Vec<f32>) {
462 let c = &self.p.cfg;
463 let h = c.hidden_size;
464 let hc = c.hc_count;
465 let normed = grouped_rms_norm(hyper, w.norm, h, eps);
466 let mut d = vec![0.0f32; w.down.out_dim];
467
468 w.down.matvec(&normed, &mut d);
469
470 for v in d.iter_mut() {
471 *v = nn::silu(*v / hc as f32);
472 }
473
474 let mut u = vec![0.0f32; w.up.out_dim];
475
476 w.up.matvec(&d, &mut u);
477
478 let mut mixed = vec![0.0f32; h];
479
480 for g in 0..hc {
481 for i in 0..h {
482 mixed[i] += nn::sigmoid(u[g * h + i]) * normed[g * h + i];
483 }
484 }
485
486 for v in mixed.iter_mut() {
487 *v /= hc as f32;
488 }
489
490 let inj = match &w.inject {
491 Some(q) => {
492 let mut r = vec![0.0f32; q.out_dim];
493
494 q.matvec(&normed, &mut r);
495
496 r.iter().map(|v| 2.0 * nn::sigmoid(v / hc as f32)).collect()
497 }
498 None => Vec::new(),
499 };
500
501 (mixed, inj)
502 }
503
504 fn moe(&self, w: &MoeWeights, x: &[f32]) -> Vec<f32> {
505 let c = &self.p.cfg;
506 let h = c.hidden_size;
507 let mut logits = bf16_matvec(w.router, c.num_experts, h, x);
509
510 nn::softmax(&mut logits);
511
512 let mut idx: Vec<usize> = (0..c.num_experts).collect();
513
514 idx.sort_by(|&a, &b| logits[b].total_cmp(&logits[a]).then(a.cmp(&b)));
515
516 let top: Vec<usize> = idx[..c.num_experts_per_tok].to_vec();
517 let sum: f32 = top.iter().map(|&i| logits[i]).sum();
518 let mut out = vec![0.0f32; h];
519
520 for &ei in &top {
521 let weight = if c.norm_topk_prob {
522 logits[ei] / sum
523 } else {
524 logits[ei]
525 };
526 let ex = self.p.expert(w.record_layer, ei);
527 let y = mlp(&ex.gate, &ex.up, &ex.down, x);
528
529 for (o, v) in out.iter_mut().zip(&y) {
530 *o += weight * v;
531 }
532 }
533
534 let shared = mlp(&w.shared_gate_proj, &w.shared_up, &w.shared_down, x);
535 let g = nn::sigmoid(bf16_dot(w.shared_gate, x));
536
537 for (o, v) in out.iter_mut().zip(&shared) {
538 *o += g * v;
539 }
540
541 out
542 }
543
544 fn attention(
545 &self,
546 w: &AttnWeights,
547 x: &[f32],
548 cache: &mut KvCache,
549 pos: usize,
550 ) -> Result<Vec<f32>> {
551 let c = &self.p.cfg;
552 let (nh, nkv, hd) = (c.num_attention_heads, c.num_key_value_heads, c.head_dim);
553 let rot = (hd as f64 * c.partial_rotary_factor) as usize;
554 let theta = c.rope_parameters.rope_theta as f32;
555 let eps = c.rms_norm_eps as f32;
556 let kv_row = nkv * hd;
557
558 let ihd = c.indexer_head_dim;
560 let inh = c.indexer_n_heads;
561 let mut qk = vec![0.0f32; w.index_qk.out_dim];
562
563 w.index_qk.matvec(x, &mut qk);
564
565 let (iq, ik) = qk.split_at(inh * ihd);
566
567 cache.index_k.extend_from_slice(ik);
568
569 let mut qg = vec![0.0f32; w.q_proj.out_dim];
570 let mut k = vec![0.0f32; w.k_proj.out_dim];
571 let mut v = vec![0.0f32; w.v_proj.out_dim];
572
573 w.q_proj.matvec(x, &mut qg);
574 w.k_proj.matvec(x, &mut k);
575 w.v_proj.matvec(x, &mut v);
576
577 for hi in 0..nh {
578 let q = &mut qg[hi * 2 * hd..hi * 2 * hd + hd];
579
580 nn::rms_norm(q, w.q_norm, eps);
581 rope_partial(q, pos, rot, theta);
582 }
583
584 for hi in 0..nkv {
585 let kh = &mut k[hi * hd..(hi + 1) * hd];
586
587 nn::rms_norm(kh, w.k_norm, eps);
588 rope_partial(kh, pos, rot, theta);
589 }
590
591 cache.k.extend_from_slice(&k);
592 cache.v.extend_from_slice(&v);
593
594 cache.len += 1;
595 let t_len = cache.len;
596
597 anyhow::ensure!(t_len == pos + 1, "attention cache out of step");
598
599 let selected = self.select_tokens(w, iq, cache, pos);
601
602 let scale = (hd as f32).powf(-0.5);
603 let group = nh / nkv;
604 let mut out = vec![0.0f32; nh * hd];
605 let mut scores = vec![0.0f32; selected.len()];
606
607 for hi in 0..nh {
608 let hk = hi / group;
609 let q = &qg[hi * 2 * hd..hi * 2 * hd + hd];
610
611 for (si, &ti) in selected.iter().enumerate() {
612 let kt = &cache.k[ti * kv_row + hk * hd..ti * kv_row + (hk + 1) * hd];
613 scores[si] = scale * q.iter().zip(kt).map(|(a, b)| a * b).sum::<f32>();
614 }
615
616 nn::softmax(&mut scores);
617
618 let oh = &mut out[hi * hd..(hi + 1) * hd];
619
620 for (si, &ti) in selected.iter().enumerate() {
621 let vt = &cache.v[ti * kv_row + hk * hd..ti * kv_row + (hk + 1) * hd];
622 let p = scores[si];
623
624 for d in 0..hd {
625 oh[d] += p * vt[d];
626 }
627 }
628
629 let gate = &qg[hi * 2 * hd + hd..(hi + 1) * 2 * hd];
630
631 for d in 0..hd {
632 oh[d] *= nn::sigmoid(gate[d]);
633 }
634 }
635
636 let mut o = vec![0.0f32; w.o_proj.out_dim];
637
638 w.o_proj.matvec(&out, &mut o);
639
640 Ok(o)
641 }
642
643 fn select_tokens(
648 &self,
649 w: &AttnWeights,
650 iq: &[f32],
651 cache: &KvCache,
652 pos: usize,
653 ) -> Vec<usize> {
654 let c = &self.p.cfg;
655 let t_len = pos + 1;
656 let ratio = c.indexer_compress_ratio;
657 let blocks = t_len / ratio;
658 let topk = c.indexer_budget / ratio;
659
660 if blocks <= topk {
661 return (0..t_len).collect();
662 }
663
664 let ihd = c.indexer_head_dim;
665 let inh = c.indexer_n_heads;
666 let rot = (c.head_dim as f64 * c.partial_rotary_factor) as usize;
667 let theta = c.rope_parameters.rope_theta as f32;
668 let eps = c.rms_norm_eps as f32;
669 let mut q = iq.to_vec();
670
671 for hi in 0..inh {
672 let qh = &mut q[hi * ihd..(hi + 1) * ihd];
673
674 nn::rms_norm(qh, w.index_q_norm, eps);
675 rope_partial(qh, pos, rot, theta);
676 }
677
678 let mut pooled = vec![0.0f32; ihd];
679 let mut block_scores: Vec<(f32, usize)> = Vec::with_capacity(blocks);
680
681 for b in 0..blocks {
682 pooled.fill(0.0);
683
684 for t in b * ratio..(b + 1) * ratio {
685 for (d, value) in pooled.iter_mut().enumerate() {
686 *value += cache.index_k[t * ihd + d];
687 }
688 }
689
690 for v in pooled.iter_mut() {
691 *v /= ratio as f32;
692 }
693
694 nn::rms_norm(&mut pooled, w.index_k_norm, eps);
695 rope_partial(&mut pooled, b * ratio, rot, theta);
696
697 let mut s = 0.0f32;
698
699 for hi in 0..inh {
700 let qh = &q[hi * ihd..(hi + 1) * ihd];
701 let dot: f32 = qh.iter().zip(&pooled).map(|(a, b)| a * b).sum();
702 s += dot.max(0.0);
703 }
704
705 block_scores.push((s / (ihd as f32).sqrt(), b));
706 }
707
708 block_scores.sort_by(|a, b| b.0.total_cmp(&a.0).then(a.1.cmp(&b.1)));
709
710 let mut sel: Vec<usize> = block_scores[..topk]
711 .iter()
712 .flat_map(|&(_, b)| b * ratio..(b + 1) * ratio)
713 .collect();
714
715 sel.extend(blocks * ratio..t_len);
716 sel.sort_unstable();
717
718 sel
719 }
720
721 fn deltanet(&self, w: &DeltaWeights, x: &[f32], state: &mut DeltaState) -> Vec<f32> {
723 let c = &self.p.cfg;
724 let (nk, nv) = (c.linear_num_key_heads, c.linear_num_value_heads);
727 let (dk, dv) = (c.linear_key_head_dim, c.linear_value_head_dim);
728 let qk_dim = nk * dk;
729 let v_dim = nv * dv;
730 let conv_dim = 2 * qk_dim + v_dim;
731 let ck = c.linear_conv_kernel_dim;
732
733 let mut qkv = vec![0.0f32; conv_dim];
734 let mut gate_input = vec![0.0f32; v_dim];
735 let mut decay_input = vec![0.0f32; nv];
736 let mut beta_input = vec![0.0f32; nv];
737
738 w.in_proj_qkv.matvec(x, &mut qkv);
739 w.in_proj_z.matvec(x, &mut gate_input);
740 w.in_proj_a.matvec(x, &mut decay_input);
741 w.in_proj_b.matvec(x, &mut beta_input);
742
743 causal_conv(&mut qkv, w.conv1d, &mut state.conv, ck);
744
745 let mut q = qkv[..qk_dim].to_vec();
747 let mut keys = qkv[qk_dim..2 * qk_dim].to_vec();
748 let v = &qkv[2 * qk_dim..];
749 let qscale = (dk as f32).powf(-0.5);
750
751 for hi in 0..nk {
752 let qh = &mut q[hi * dk..(hi + 1) * dk];
753
754 nn::l2_norm(qh, 1e-6);
755
756 for qv in qh.iter_mut() {
757 *qv *= qscale;
758 }
759
760 nn::l2_norm(&mut keys[hi * dk..(hi + 1) * dk], 1e-6);
761 }
762
763 let group = nv / nk;
764 let mut y = vec![0.0f32; v_dim];
765 let mut kv_mem = vec![0.0f32; dv];
766 let mut delta = vec![0.0f32; dv];
767 let sigmoid_gate = c.output_gate_type == "sigmoid";
768
769 for hv in 0..nv {
770 let hk = hv / group;
771 let qh = &q[hk * dk..(hk + 1) * dk];
772 let kh = &keys[hk * dk..(hk + 1) * dk];
773 let beta = nn::sigmoid(beta_input[hv]);
774 let g = -(w.a_log[hv].to_f32().exp())
775 * nn::softplus(decay_input[hv] + w.dt_bias[hv].to_f32());
776 let decay = g.exp();
777 let s = &mut state.s[hv * dk * dv..(hv + 1) * dk * dv];
780
781 kv_mem.fill(0.0);
783
784 for ik in 0..dk {
785 let row = &mut s[ik * dv..(ik + 1) * dv];
786 let kw = kh[ik];
787
788 for iv in 0..dv {
789 row[iv] *= decay;
790 kv_mem[iv] += row[iv] * kw;
791 }
792 }
793
794 for iv in 0..dv {
796 delta[iv] = (v[hv * dv + iv] - kv_mem[iv]) * beta;
797 }
798
799 let yh = &mut y[hv * dv..(hv + 1) * dv];
802
803 for ik in 0..dk {
804 let row = &mut s[ik * dv..(ik + 1) * dv];
805 let kw = kh[ik];
806 let qw = qh[ik];
807
808 for iv in 0..dv {
809 row[iv] += kw * delta[iv];
810 yh[iv] += row[iv] * qw;
811 }
812 }
813
814 nn::rms_norm(yh, w.norm, 1e-6);
815
816 for iv in 0..dv {
817 let zz = gate_input[hv * dv + iv];
818 yh[iv] *= if sigmoid_gate {
819 nn::sigmoid(zz)
820 } else {
821 nn::silu(zz)
822 };
823 }
824 }
825
826 let mut o = vec![0.0f32; w.out_proj.out_dim];
827
828 w.out_proj.matvec(&y, &mut o);
829
830 o
831 }
832
833 fn ngram_ids(&self, w: &PleWeights, tokens: &[u32]) -> Vec<u64> {
834 Self::ngram_ids_from(
835 &self.p.cfg,
836 &w.multipliers,
837 &w.head_offsets,
838 &w.head_sizes,
839 tokens,
840 )
841 }
842
843 pub fn ngram_ids_from(
847 c: &super::Qwen4ExpConfig,
848 multipliers: &[i64],
849 head_offsets: &[u64],
850 head_sizes: &[u64],
851 tokens: &[u32],
852 ) -> Vec<u64> {
853 let eos = c.eos_token_id;
854 let t = tokens.len() - 1;
855 let shifted: Vec<i64> = (0..c.ngram_size)
856 .map(|k| {
857 if k == 0 {
858 return tokens[t] as i64;
859 }
860
861 if t < k || tokens[t - k..t].contains(&eos) {
862 eos as i64
863 } else {
864 tokens[t - k] as i64
865 }
866 })
867 .collect();
868 let mut ids = Vec::with_capacity(head_offsets.len());
869
870 for ngram in 2..=c.ngram_size {
871 let mut mixed = shifted[0].wrapping_mul(multipliers[0]);
872
873 for pos in 1..ngram {
874 mixed ^= shifted[pos].wrapping_mul(multipliers[pos]);
875 }
876
877 let start = (ngram - 2) * c.heads_per_ngram;
878
879 for head in start..start + c.heads_per_ngram {
880 let size = head_sizes[head] as i64;
881
882 ids.push(mixed.rem_euclid(size) as u64 + head_offsets[head]);
883 }
884 }
885
886 ids
887 }
888
889 fn ple(&self, w: &PleWeights, hyper: &[f32], state: &mut State) -> Result<Vec<f32>> {
892 let c = &self.p.cfg;
893 let h = c.hidden_size;
894 let hc = c.hc_count;
895 let hh = h * hc;
896 let eps = c.rms_norm_eps as f32;
897 let dim = self.p.manifest.ngram.dim;
898 let ids = self.ngram_ids(w, &state.tokens);
899
900 anyhow::ensure!(
901 ids.len() * dim == c.ple_embed_dim,
902 "n-gram head layout mismatch"
903 );
904
905 let mut e = vec![0.0f32; c.ple_embed_dim];
906
907 for (hi, &id) in ids.iter().enumerate() {
908 self.p.ngram_row(id, &mut e[hi * dim..(hi + 1) * dim]);
909 }
910
911 let mut key = vec![0.0f32; w.key_proj.out_dim];
912
913 w.key_proj.matvec(&e, &mut key);
914
915 let key = grouped_rms_norm(&key, w.norm_key, h, eps);
916 let mut value = vec![0.0f32; w.value_proj.out_dim];
917
918 w.value_proj.matvec(&e, &mut value);
919
920 let query = grouped_rms_norm(hyper, w.norm_query, h, eps);
921 let mut gated = vec![0.0f32; hh];
922
923 for g in 0..hc {
924 let dot: f32 = key[g * h..(g + 1) * h]
925 .iter()
926 .zip(&query[g * h..(g + 1) * h])
927 .map(|(a, b)| a * b)
928 .sum::<f32>()
929 / (h as f32).sqrt();
930 let gate = dot.abs().max(1e-6).sqrt() * dot.signum();
931 let s = nn::sigmoid(gate);
932
933 for i in 0..h {
934 gated[g * h + i] = s * value[i];
935 }
936 }
937
938 let gvn = grouped_rms_norm(&gated, w.norm_conv, h, eps);
939 let ps = state.ple.as_mut().context("PLE state missing")?;
942 let span = ps.span;
943 let mut out = gated;
944
945 for ch in 0..hh {
946 let wrow = &w.conv1d[ch * w.kernel..(ch + 1) * w.kernel];
947 let mut acc = wrow[w.kernel - 1].to_f32() * gvn[ch];
948
949 for (k, weight) in wrow.iter().take(w.kernel - 1).enumerate() {
950 let back = w.dilation * (w.kernel - 1 - k); if back <= ps.filled {
953 let slot = (ps.filled - back) % span;
954 acc += weight.to_f32() * ps.hist[slot * hh + ch];
955 }
956 }
957
958 out[ch] += nn::silu(acc);
959 }
960
961 let slot = ps.filled % span;
962
963 ps.hist[slot * hh..(slot + 1) * hh].copy_from_slice(&gvn);
964
965 ps.filled += 1;
966
967 Ok(out)
968 }
969}
970
971fn inject(hyper: &mut [f32], out: &[f32], inj: &[f32]) {
972 let h = out.len();
973
974 for (g, &wg) in inj.iter().enumerate() {
975 for i in 0..h {
976 hyper[g * h + i] += out[i] * wg;
977 }
978 }
979}
980
981fn mlp(gate: &QLinear, up: &QLinear, down: &QLinear, x: &[f32]) -> Vec<f32> {
982 let mut g = vec![0.0f32; gate.out_dim];
983 let mut u = vec![0.0f32; up.out_dim];
984
985 gate.matvec(x, &mut g);
986 up.matvec(x, &mut u);
987
988 for (gv, uv) in g.iter_mut().zip(&u) {
989 *gv = nn::silu(*gv) * uv;
990 }
991
992 let mut out = vec![0.0f32; down.out_dim];
993
994 down.matvec(&g, &mut out);
995
996 out
997}
998
999fn rms_norm_shift(x: &[f32], w: &[bf16], eps: f32) -> Vec<f32> {
1001 let ms = x.iter().map(|v| v * v).sum::<f32>() / x.len() as f32;
1002 let inv = 1.0 / (ms + eps).sqrt();
1003
1004 x.iter()
1005 .zip(w)
1006 .map(|(v, w)| v * inv * (1.0 + w.to_f32()))
1007 .collect()
1008}
1009
1010fn grouped_rms_norm(x: &[f32], w: &[bf16], group: usize, eps: f32) -> Vec<f32> {
1013 debug_assert_eq!(x.len(), w.len());
1014
1015 let mut out = vec![0.0f32; x.len()];
1016
1017 for (g, chunk) in x.chunks_exact(group).enumerate() {
1018 let ms = chunk.iter().map(|v| v * v).sum::<f32>() / group as f32;
1019 let inv = 1.0 / (ms + eps).sqrt();
1020
1021 for (i, v) in chunk.iter().enumerate() {
1022 out[g * group + i] = v * inv * w[g * group + i].to_f32();
1023 }
1024 }
1025
1026 out
1027}
1028
1029fn bf16_matvec(w: &[bf16], rows: usize, cols: usize, x: &[f32]) -> Vec<f32> {
1030 debug_assert_eq!(w.len(), rows * cols);
1031 debug_assert_eq!(x.len(), cols);
1032
1033 (0..rows)
1034 .into_par_iter()
1035 .map(|r| {
1036 w[r * cols..(r + 1) * cols]
1037 .iter()
1038 .zip(x)
1039 .map(|(a, b)| a.to_f32() * b)
1040 .sum()
1041 })
1042 .collect()
1043}
1044
1045fn bf16_dot(w: &[bf16], x: &[f32]) -> f32 {
1046 w.iter().zip(x).map(|(a, b)| a.to_f32() * b).sum()
1047}
1048
1049fn rope_partial(x: &mut [f32], pos: usize, rot: usize, theta: f32) {
1052 let half = rot / 2;
1053
1054 for j in 0..half {
1055 let inv_freq = theta.powf(-(2.0 * j as f32) / rot as f32);
1056 let angle = pos as f32 * inv_freq;
1057 let (sin, cos) = angle.sin_cos();
1058 let a = x[j];
1059 let b = x[j + half];
1060 x[j] = a * cos - b * sin;
1061 x[j + half] = b * cos + a * sin;
1062 }
1063}
1064
1065fn causal_conv(qkv: &mut [f32], weights: &[bf16], history: &mut [f32], ck: usize) {
1068 let km1 = ck - 1;
1069
1070 for (ch, value) in qkv.iter_mut().enumerate() {
1071 let wrow = &weights[ch * ck..(ch + 1) * ck];
1072 let hist = &mut history[ch * km1..(ch + 1) * km1];
1073 let cur = *value;
1074 let mut acc = wrow[km1].to_f32() * cur;
1075
1076 for j in 0..km1 {
1077 acc += wrow[j].to_f32() * hist[j];
1078 }
1079
1080 hist.rotate_left(1);
1081
1082 hist[km1 - 1] = cur;
1083 *value = nn::silu(acc);
1084 }
1085}